/*
 * Copyright (C) 2010-2011 Comcast Cable.  All Rights Reserved.
 */
package com.hercules.android.receiver.connection.parser.json;

import com.google.gjson.JSONArray;
import com.google.gjson.JSONException;
import com.google.gjson.JSONObject;
import com.google.gjson.JSONString;
import com.hercules.android.receiver.LogUtil;
import com.hercules.android.receiver.connection.parser.CommandParsingException;
import com.hercules.android.receiver.connection.parser.MalformedCommandException;
import com.hercules.android.receiver.connection.parser.json.itemparsers.ArrayParser;
import com.hercules.android.receiver.connection.parser.json.itemparsers.BooleanParser;
import com.hercules.android.receiver.connection.parser.json.itemparsers.DefaultParser;
import com.hercules.android.receiver.connection.parser.json.itemparsers.DoubleParser;
import com.hercules.android.receiver.connection.parser.json.itemparsers.EnumParser;
import com.hercules.android.receiver.connection.parser.json.itemparsers.EventParser;
import com.hercules.android.receiver.connection.parser.json.itemparsers.FieldParser;
import com.hercules.android.receiver.connection.parser.json.itemparsers.IntegerParser;
import com.hercules.android.receiver.connection.parser.json.itemparsers.ListParser;
import com.hercules.android.receiver.connection.parser.json.itemparsers.MapParser;
import com.hercules.android.receiver.connection.parser.json.itemparsers.MatrixParser;
import com.hercules.android.receiver.connection.parser.json.itemparsers.ObjectParser;
import com.hercules.android.receiver.connection.parser.json.itemparsers.RuntimePropertyParser;
import com.hercules.android.receiver.connection.parser.json.itemparsers.StringParser;
import com.hercules.android.receiver.model.commands.XRECallCommand;
import com.hercules.android.receiver.model.commands.XRENewCommand;
import com.hercules.android.receiver.model.commands.XRESetCommand;
import com.hercules.android.receiver.model.objects.XREAnimationEasingFunction;
import com.hercules.android.receiver.model.objects.XREApplication;
import com.hercules.android.receiver.model.objects.XREHorizontalAlignment;
import com.hercules.android.receiver.model.objects.XREImageQuality;
import com.hercules.android.receiver.model.objects.XREResourceOptions;
import com.hercules.android.receiver.model.objects.XREResourceStretch;
import com.hercules.android.receiver.model.objects.XRETextTruncStyle;
import com.hercules.android.receiver.model.objects.XRETextWrap;
import com.hercules.android.receiver.model.objects.XREVerticalAlignment;
import com.hercules.android.receiver.model.objects.XREVideo;
import com.hercules.android.receiver.model.objects.XREView;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.logging.Logger;

/**
 * Represents JSON item parsed. It can parse field in JSON object or item in JSON array. It also sets value to target
 * instance depending of the value of {@link SetterType} enum.
 * <p>
 * AndroidAssembly2 this class was added in Android Assembly 2
 * <p>
 * <b>Thread safety:</b> This class is thread safe, subclasses are required to be thread safe.
 *
 * @author 7realm
 * @version 1.0
 */
public abstract class ItemParser {
    /** Represents parser for array of doubles. */
    private static final ArrayParser DOUBLE_ARRAY = new ArrayParser(true, double.class, 2);
    /** Represents parser for 'matrix' property. */
    protected static final MatrixParser MATRIX = new MatrixParser();
    /** Represents parser for 'dimensions' property. */
    protected static final ArrayParser DIMENSIONS = new ArrayParser(false, int.class, 2);
    /** Represents parser for double optional property. */
    protected static final DoubleParser DOUBLE_OPTIONAL = new DoubleParser(false);
    /** Represents parser for double required property. */
    protected static final DoubleParser DOUBLE_REQUIRED = new DoubleParser(true);
    /** Represents parser for string optional property. */
    protected static final StringParser STRING_OPTIONAL = new StringParser(false);
    /** Represents parser for string required property. */
    protected static final StringParser STRING_REQUIRED = new StringParser(true);
    /** Represents parser for boolean optional property. */
    protected static final BooleanParser BOOLEAN_OPTIONAL = new BooleanParser(false);
    /** Represents parser for boolean required property. */
    protected static final BooleanParser BOOLEAN_REQUIRED = new BooleanParser(true);
    /** Represents parser for integer optional property. */
    protected static final IntegerParser INTEGER_OPTIONAL = new IntegerParser(false);
    /** Represents parser for integer required property. */
    protected static final IntegerParser INTEGER_REQUIRED = new IntegerParser(true);

    /**
     * Represents empty parser to ignore certain properties.
     * <p>
     * AndroidAssembly3 new field
     */
    private static final ItemParser EMPTY_PARSER = new ItemParser(false) {
        @Override
        public Object parse(StringBuilder path, JSONArray jsonArray, int index, Object instance, SetterType setterType) {
            return null;
        }

        @Override
        public Object parse(StringBuilder path, JSONObject json, String keyName, Object instance, SetterType setterType) {
            return null;
        }
    };

    /** Represents map of parsers. Key is path of value in command and value is corresponding parser. */
    protected static final Map<String, ItemParser> PARSERS = initParsers();

    /** Represents logger, used for logging. */
    private static final Logger LOGGER = LogUtil.getLogger(ItemParser.class.getSimpleName());

    /** Indicates if given property is required. */
    private final boolean required;

    /**
     * Protected constructor for {@link ItemParser}.
     *
     * @param required if property is required
     */
    protected ItemParser(boolean required) {
        this.required = required;
    }

    /**
     * Gets value of required field.
     *
     * @return the value of required field
     */
    public boolean isRequired() {
        return required;
    }

    /**
     * Parses JSON key value.
     *
     * @param path the path to value
     * @param json the JSON
     * @param keyName the name of current JSON key
     * @param instance the instance of object, parsed value will be set to it
     * @param setterType describes how value should be set to instance
     * @return the parsed value
     * @throws CommandParsingException if error occurs while parsing
     */
    public abstract Object parse(StringBuilder path, JSONObject json, String keyName, Object instance,
        SetterType setterType) throws CommandParsingException;

    /**
     * Parses JSON array item value.
     *
     * @param path the path to value
     * @param jsonArray the JSON array
     * @param index the name of current item in array
     * @param instance the instance of object, parsed value will be set to it
     * @param setterType describes how value should be set to instance
     * @return the parsed value
     * @throws CommandParsingException if error occurs while parsing
     */
    public abstract Object parse(StringBuilder path, JSONArray jsonArray, int index, Object instance,
        SetterType setterType) throws CommandParsingException;

    /**
     * Sets parsed value to given instance.
     *
     * @param path the path to value
     * @param keyName the name of current JSON key
     * @param instance the instance of object, parsed value will be set to it
     * @param value the parsed value
     * @param setterType describes how value should be set to instance
     * @throws CommandParsingException if error occurs while setting value
     */
    @SuppressWarnings("unchecked")
    protected void setValue(StringBuilder path, String keyName, Object instance, Object value, SetterType setterType)
        throws CommandParsingException {
        switch (setterType) {
        case LIST:
            if (instance instanceof List) {
                List list = (List) instance;

                // add value to list
                list.add(value);
            } else {
                throw new CommandParsingException("List setter expected instance of type List, but was '" + instance
                    + "'. Path '" + path + "'.");
            }
            break;
        case MAP:
            if (instance instanceof Map) {
            	Map map = (Map) instance;

                // add value to list
                map.put(keyName, value);
            } else {
                throw new CommandParsingException("Map setter expected instance of type Map, but was '" + instance
                    + "'. Path '" + path + "'.");
            }
            break;
        default:
            // find field
            Field field = null;
            Class<?> fieldHolderClass = instance.getClass();
            while (!fieldHolderClass.equals(Object.class)) {
                try {
                    field = fieldHolderClass.getDeclaredField(keyName);
                    break;
                } catch (SecurityException e) {
                    throw new CommandParsingException("Security error occurred while getting field '" + keyName
                        + "' of '" + instance + "'. Path '" + path + "'.", e);
                } catch (NoSuchFieldException e) {
                    // continue
                    fieldHolderClass = fieldHolderClass.getSuperclass();
                }
            }
            if (field == null) {
                throw new CommandParsingException("Could not find field '" + keyName + "' in '" + instance
                    + "'. Path '" + path + "'.");
            }

            // set field value
            field.setAccessible(true);
            try {
                field.set(instance, value);
            } catch (IllegalArgumentException e) {
                throw new CommandParsingException("Error occurred while setting value of the field '" + keyName
                    + "' of '" + instance + "'. Path '" + path + "'.", e);
            } catch (IllegalAccessException e) {
                // will never occur
                throw new CommandParsingException("Error occurred while setting value of the field '" + keyName
                    + "' of '" + instance + "'. Path '" + path + "'.", e);
            }
            break;
        }
    }

    protected void parseMap(StringBuilder path, JSONObject json, Object instance, SetterType setterType) 
    	throws CommandParsingException {
    	parseMap(path, json, instance, setterType, Collections.<String>emptySet());
    }
    /**
     * Helper method for parsing map.
     *
     * @param path the path to map property
     * @param json the JSON object that represent map
     * @param instance the target instance
     * @param setterType describes how value should be set to instance
     * @throws CommandParsingException if error occurs while parsing map
     */
    protected void parseMap(StringBuilder path, JSONObject json, Object instance, SetterType setterType, Set<String> exceptionKeys)
        throws CommandParsingException {
        // get sub map
        String prefix = path.toString();

        // check the case, when we have only one parser
        ItemParser commonParser = null;
        if (PARSERS.containsKey(prefix + ".*")) {
            commonParser = PARSERS.get(prefix + ".*");
        }

        // prepare list of already parsed items
        Set<String> parsed = new HashSet<String>();

        // parse command's properties
        Set<String> keys = json.keySet();
        for (String key : keys) {
        	if (exceptionKeys.contains(key)) {
        		continue;
        	}
        	
            // build inner path
        	StringBuilder innerPath = join(path, key);

        	String innerPathString = innerPath.toString();
            if (parsed.contains(innerPathString)) {
                continue;
            }
            parsed.add(innerPathString);

            // get parser, use common parser if it s present
            ItemParser parser = commonParser != null ? commonParser : PARSERS.get(innerPathString);
            if (parser == null) {
                warn(this, "No parser for {0} in JSON.", innerPathString);
                continue;
            }

            // parse
            parser.parse(innerPath, json, key, instance, setterType);
        }
    }

    /**
     * Logs warning message.
     *
     * @param parser the current parser
     * @param message the message
     * @param params the message parameters
     */
    protected static void warn(ItemParser parser, String message, Object... params) {
        LogUtil.warn(LOGGER, parser, "parse", message, params);
    }

    /**
     * Get string value for JSON
     *
     * @param json the JSON object
     * @param keyName the name of required JSON key
     * @param exceptionMessage the exception message if property cannot be retrieved
     * @return the value of string property
     * @throws MalformedCommandException if error occurs while parsing
     */
    protected static String getStringValue(JSONObject json, String keyName, String exceptionMessage)
        throws MalformedCommandException {
        try {
        	if (!json.containsKey(keyName)) {
        		throw new MalformedCommandException(exceptionMessage);
        	}
        	
        	JSONString result = json.get(keyName).isString();
            if (result == null || result.isNull() != null) {
                throw new MalformedCommandException(exceptionMessage);
            }
            return result.stringValue();
        } catch (JSONException e) {
            // will never occur, because existence of command name is checked before this call
            throw new MalformedCommandException(exceptionMessage, e);
        }
    }

    /**
     * Add next item to path.
     *
     * @param path the path
     * @param next the item to add
     * @return the new path with added item
     */
    protected static StringBuilder join(StringBuilder path, String next) {
    	StringBuilder result = new StringBuilder();
    	if (path.length() > 0) {
    		result.append(path);
    		result.append('.');
    	}
    	result.append(next);
    	return result;
    }

    /**
     * Initializes parser map.
     *
     * @return the initialized parser map
     */
    private static Map<String, ItemParser> initParsers() {
        Map<String, ItemParser> map = new LinkedHashMap<String, ItemParser>();
        // connect command
        map.put("CONNECT.version", STRING_REQUIRED);
        map.put("CONNECT.sessionGUID", STRING_REQUIRED);
        map.put("CONNECT.keyMapURL", STRING_REQUIRED);

        // connect reject command
        map.put("CONNECT_REJECTED.reason", STRING_OPTIONAL);
        map.put("CONNECT_REJECTED.statusCode", INTEGER_REQUIRED);

        // restart command
        map.put("RESTART.reason", STRING_OPTIONAL);
        map.put("RESTART.statusCode", INTEGER_REQUIRED);

        // redirect command
        map.put("REDIRECT.url", STRING_REQUIRED);
        map.put("REDIRECT.preserveSession", new BooleanParser(false));
        map.put("REDIRECT.keyMapURL", STRING_REQUIRED);

        // shutdown command is expected to be empty

        // delete command
        map.put("DELETE.targetId", INTEGER_OPTIONAL);
        map.put("DELETE.targetPath", STRING_OPTIONAL);

        // AndroidAssembly3 added GET command support
        // get command
        map.put("GET.targetId", INTEGER_OPTIONAL);
        map.put("GET.targetPath", STRING_OPTIONAL);
        map.put("GET.context", STRING_OPTIONAL);
        map.put("GET.props", new ListParser(true, null));
        map.put("GET.props.*", STRING_OPTIONAL);

        // put new, set and call commands
        putNewCommand(map);
        putSetCommand(map);
        putCallCommand(map);

        // add dummy commands
        map.put("CONNECT.targetId", EMPTY_PARSER);
        map.put("CONNECT.targetPath", EMPTY_PARSER);
        map.put("NEW.targetId", EMPTY_PARSER);
        map.put("NEW.targetPath", EMPTY_PARSER);

        map.put("CONNECT.appId", EMPTY_PARSER);
        map.put("NEW.appId", EMPTY_PARSER);
        map.put("SET.appId", EMPTY_PARSER);
        map.put("CALL.appId", EMPTY_PARSER);
        map.put("DELETE.appId", EMPTY_PARSER);
        map.put("GET.appId", EMPTY_PARSER);

        map.put("CALL.callGUID", EMPTY_PARSER);

        map.put("START_TIMER.reason", STRING_OPTIONAL);
        map.put("START_TIMER.statusCode", INTEGER_OPTIONAL);
        map.put("START_TIMER.appId", EMPTY_PARSER);
        map.put("START_TIMER.targetId", INTEGER_OPTIONAL);
        map.put("START_TIMER.targetPath", STRING_OPTIONAL);
        map.put("START_TIMER.method", STRING_REQUIRED);
        
        return map;
    }

    /**
     * Fills rules for {@link XRENewCommand}.
     *
     * @param map the map of rules
     */
    private static void putNewCommand(Map<String, ItemParser> map) {
        map.put("NEW.id", INTEGER_REQUIRED);
        map.put("NEW.klass", STRING_REQUIRED);
        map.put("NEW.params", new MapParser(true, "klass"));

        // XRECommandSequence parameters
        map.put("NEW.params.XRECommandSequence.commands", new ListParser(false, null));
        map.put("NEW.params.XRECommandSequence.commands.*", new CommandParser(false));

        // XREView parameters
        map.put("NEW.params.XREView.parent", INTEGER_OPTIONAL);
        map.put("NEW.params.XREView.alpha", DOUBLE_OPTIONAL);
        map.put("NEW.params.XREView.name", STRING_OPTIONAL);
        map.put("NEW.params.XREView.dimensions", DIMENSIONS);
        map.put("NEW.params.XREView.matrix", MATRIX);
        map.put("NEW.params.XREView.clip", BOOLEAN_OPTIONAL);
        map.put("NEW.params.XREView.resource", INTEGER_OPTIONAL);
        putResourceOptions(map, "NEW.params.XREView");
        map.put("NEW.params.XREView.mask", INTEGER_OPTIONAL);
        map.put("NEW.params.XREView.ignoreMouse", BOOLEAN_OPTIONAL);
        map.put("NEW.params.XREView.visible", BOOLEAN_OPTIONAL);
        map.put("NEW.params.XREView.painting", BOOLEAN_OPTIONAL);
        map.put("NEW.params.XREView.paintPropertyDisabled", BOOLEAN_OPTIONAL);
        putXREViewEvents(map, "NEW.params.XREView");

        // XREText parameters
        map.put("NEW.params.XREText.text", STRING_OPTIONAL);
        map.put("NEW.params.XREText.font", INTEGER_OPTIONAL);
        map.put("NEW.params.XREText.color", INTEGER_OPTIONAL);
        map.put("NEW.params.XREText.size", INTEGER_OPTIONAL);
        map.put("NEW.params.XREText.blinkingCursor", BOOLEAN_OPTIONAL);
        map.put("NEW.params.XREText.leading", INTEGER_OPTIONAL);
        map.put("NEW.params.XREText.documentMargin", DOUBLE_OPTIONAL);
        putEventParser(map, "NEW.params.XREText.onTextMetadata");

        // XREHTMLText parameters
        map.put("NEW.params.XREHTMLText.htmlText", STRING_OPTIONAL);
        map.put("NEW.params.XREHTMLText.stylesheet", INTEGER_OPTIONAL);
        map.put("NEW.params.XREHTMLText.leading", INTEGER_OPTIONAL);
        putEventParser(map, "NEW.params.XREHTMLText.onHTMLLinkClicked");

        // AndroidAssembly3 added support for HTMLXREView
        // XREHTMLView parameters
        map.put("NEW.params.XREHTMLView.url", STRING_REQUIRED);
        map.put("NEW.params.XREHTMLView.speed", DOUBLE_OPTIONAL);
        map.put("NEW.params.XREHTMLView.position", INTEGER_OPTIONAL);
        map.put("NEW.params.XREHTMLView.contentType", STRING_OPTIONAL);
        map.put("NEW.params.XREHTMLView.navigationMode", new ObjectParser(false, Object.class));
        putEventParser(map, "NEW.params.XREHTMLText.onHTMLLinkClicked");
        putEventParser(map, "NEW.params.XREHTMLText.onScroll");

        // XREStylesheet parameters
        map.put("NEW.params.XREStylesheet.css", STRING_REQUIRED);

        // XRERectangle parameters
        map.put("NEW.params.XRERectangle.color", INTEGER_OPTIONAL);
        map.put("NEW.params.XRERectangle.borderColor", INTEGER_OPTIONAL);
        map.put("NEW.params.XRERectangle.borderThickness", DOUBLE_OPTIONAL);

        // XREFont parameters
        map.put("NEW.params.XREFont.family", STRING_OPTIONAL);
        map.put("NEW.params.XREFont.style", STRING_OPTIONAL);

        // XREImage parameters
        putImageCommons(map, "NEW.params.XREImage");

        // XRENineSliceImage parameters
        putImageCommons(map, "NEW.params.XRENineSliceImage");
        map.put("NEW.params.XRENineSliceImage.left", INTEGER_OPTIONAL);
        map.put("NEW.params.XRENineSliceImage.right", INTEGER_OPTIONAL);
        map.put("NEW.params.XRENineSliceImage.top", INTEGER_OPTIONAL);
        map.put("NEW.params.XRENineSliceImage.bottom", INTEGER_OPTIONAL);

        // XRESound parameters
        map.put("NEW.params.XRESound.url", STRING_REQUIRED);
        map.put("NEW.params.XRESound.volume", INTEGER_OPTIONAL);
        map.put("NEW.params.XRESound.position", INTEGER_OPTIONAL);
        map.put("NEW.params.XRESound.autoPlay", BOOLEAN_OPTIONAL);
        map.put("NEW.params.XRESound.speed", DOUBLE_OPTIONAL);
        map.put("NEW.params.XRESound.contentType", STRING_OPTIONAL);

        // XREFlash parameters
        map.put("NEW.params.XREFlash.url", STRING_REQUIRED);
        map.put("NEW.params.XREFlash.flashvars", new MapParser(false, null));
        putEventParser(map, "NEW.params.XREFlash.onFlashMetadata");
        putEventParser(map, "NEW.params.XREFlash.onFlashEvent");

        // XREVideo parameters
        map.put("NEW.params.XREVideo.url", STRING_REQUIRED);
        map.put("NEW.params.XREVideo.volume", INTEGER_OPTIONAL);
        map.put("NEW.params.XREVideo.autoPlay", BOOLEAN_OPTIONAL);
        map.put("NEW.params.XREVideo.speed", DOUBLE_OPTIONAL);
        map.put("NEW.params.XREVideo.contentType", STRING_OPTIONAL);
        // AndroidAssembly3 added support for XREVideo content options
        map.put("NEW.params.XREVideo.contentOptions", new ListParser(true, null));
        map.put("NEW.params.XREVideo.contentOptions.*", new MapParser(true, null));
        map.put("NEW.params.XREVideo.contentOptions.*.*", STRING_OPTIONAL);
        map.put("NEW.params.XREVideo.position", INTEGER_OPTIONAL);
        putVideoEvents(map, "NEW.params.XREVideo");

        // XREApplication parameters
        map.put("NEW.params.XREApplication.url", STRING_REQUIRED);
        map.put("NEW.params.XREApplication.minimumVersion", STRING_OPTIONAL);
        map.put("NEW.params.XREApplication.sessionGUID", STRING_OPTIONAL);
        map.put("NEW.params.XREApplication.launchParams", new MapParser(false, null));
        map.put("NEW.params.XREApplication.launchParams.*", new DefaultParser(false));
        putApplicationEvents(map, "NEW.params.XREApplication");

        // XRETextInput parameters
        map.put("NEW.params.XRETextInput.text", STRING_OPTIONAL);
        map.put("NEW.params.XRETextInput.maskText", BOOLEAN_OPTIONAL);
        map.put("NEW.params.XRETextInput.font", INTEGER_OPTIONAL);
        map.put("NEW.params.XRETextInput.color", INTEGER_OPTIONAL);
        map.put("NEW.params.XRETextInput.size", INTEGER_OPTIONAL);
        putEventParser(map, "NEW.params.XRETextInput.onChanged");

        // XREAlphaAnimation parameters
        putAnimationCommons(map, "NEW.params.XREAlphaAnimation");
        map.put("NEW.params.XREAlphaAnimation.alpha", DOUBLE_REQUIRED);

        // XREDimensionsAnimation parameters
        putAnimationCommons(map, "NEW.params.XREDimensionsAnimation");
        map.put("NEW.params.XREDimensionsAnimation.width", INTEGER_REQUIRED);
        map.put("NEW.params.XREDimensionsAnimation.height", INTEGER_REQUIRED);

        // XRETransformAnimation parameters
        putAnimationCommons(map, "NEW.params.XRETransformAnimation");
        map.put("NEW.params.XRETransformAnimation.x", INTEGER_OPTIONAL);
        map.put("NEW.params.XRETransformAnimation.y", INTEGER_OPTIONAL);
        map.put("NEW.params.XRETransformAnimation.scaleX", DOUBLE_OPTIONAL);
        map.put("NEW.params.XRETransformAnimation.scaleY", DOUBLE_OPTIONAL);
        map.put("NEW.params.XRETransformAnimation.rotation", DOUBLE_OPTIONAL);
        map.put("NEW.params.XRETransformAnimation.actionPointX", INTEGER_OPTIONAL);
        map.put("NEW.params.XRETransformAnimation.actionPointY", INTEGER_OPTIONAL);

        // XREAbsoluteScaleAnimation parameters
        putAnimationCommons(map, "NEW.params.XREAbsoluteScaleAnimation");
        map.put("NEW.params.XREAbsoluteScaleAnimation.sx", INTEGER_REQUIRED);
        map.put("NEW.params.XREAbsoluteScaleAnimation.sy", INTEGER_REQUIRED);

        // AndroidAssembly3 added support for XREAbsoluteTranslationAnimation
        // XREAbsoluteTranslationAnimation parameters
        putAnimationCommons(map, "NEW.params.XREAbsoluteTranslationAnimation");
        map.put("NEW.params.XREAbsoluteTranslationAnimation.x", INTEGER_REQUIRED);
        map.put("NEW.params.XREAbsoluteTranslationAnimation.y", INTEGER_REQUIRED);
    }

    /**
     * Fills rules for {@link XRESetCommand}.
     *
     * @param map the map of rules
     */
    private static void putSetCommand(Map<String, ItemParser> map) {
        map.put("SET.targetId", INTEGER_OPTIONAL);
        map.put("SET.targetPath", STRING_OPTIONAL);
        map.put("SET.props", new MapParser(true, null));

        // XREView runtime properties
        map.put("SET.props.alpha", DOUBLE_OPTIONAL);
        map.put("SET.props.name", STRING_OPTIONAL);
        map.put("SET.props.dimensions", DIMENSIONS);
        map.put("SET.props.matrix", MATRIX);
        map.put("SET.props.clip", BOOLEAN_OPTIONAL);
        map.put("SET.props.resource", INTEGER_OPTIONAL);
        putResourceOptions(map, "SET.props");
        map.put("SET.props.mask", INTEGER_OPTIONAL);
        map.put("SET.props.ignoreMouse", BOOLEAN_OPTIONAL);
        map.put("SET.props.visible", BOOLEAN_OPTIONAL);
        map.put("SET.props.painting", BOOLEAN_OPTIONAL);
        putXREViewEvents(map, "SET.props");

        // XREText runtime properties
        map.put("SET.props.text", STRING_OPTIONAL);
        map.put("SET.props.size", INTEGER_OPTIONAL);
        map.put("SET.props.color", INTEGER_OPTIONAL);
        map.put("SET.props.blinkingCursor", BOOLEAN_OPTIONAL);
        map.put("SET.props.documentMargin", DOUBLE_OPTIONAL);
        putEventParser(map, "SET.props.onTextMetadata");

        // XREHTMLText runtime properties
        map.put("SET.props.htmlText", STRING_OPTIONAL);
        putEventParser(map, "SET.props.onHTMLLinkClicked");

        // XREHTMLView runtime properties
        putEventParser(map, "SET.props.onHTMLLinkClicked");
        putEventParser(map, "SET.props.onScroll");

        // XREStylesheet runtime properties
        map.put("SET.props.css", STRING_OPTIONAL);

        // XRERectangle runtime properties
        map.put("SET.props.color", INTEGER_OPTIONAL);
        map.put("SET.props.borderColor", INTEGER_OPTIONAL);
        map.put("SET.props.borderThickness", DOUBLE_OPTIONAL);

        // no runtime properties for XREFont

        // XREImage runtime properties
        map.put("SET.props.url", STRING_OPTIONAL);
        putEventParser(map, "SET.props.onImageMetadata");
        putEventParser(map, "SET.props.onReady");

        // XRENineSliceImage runtime properties
        putEventParser(map, "SET.props.onImageMetadata");

        // XRESound runtime properties
        map.put("SET.props.volume", INTEGER_OPTIONAL);
        map.put("SET.props.position", INTEGER_OPTIONAL);
        map.put("SET.props.speed", DOUBLE_OPTIONAL);

        // XREFlash runtime properties
        putEventParser(map, "SET.props.onFlashMetadata");
        putEventParser(map, "SET.props.onFlashEvent");

        // XREVideo runtime properties are same as XRESound
        putVideoEvents(map, "SET.props");

        // XREApplication runtime properties
        map.put("SET.props.launchParams", new MapParser(false, null));
        map.put("SET.props.launchParams.*", new DefaultParser(false));
        putApplicationEvents(map, "SET.props");

        // XRETextInput runtime properties
        map.put("SET.props.text", STRING_OPTIONAL);
        putEventParser(map, "SET.props.onChanged");

        // XREAnimations runtime properties
        putEventParser(map, "SET.props.onComplete");

        // add runtime property wrappers
        Map<String, ItemParser> newMap = new HashMap<String, ItemParser>();
        for (Entry<String, ItemParser> entry : map.entrySet()) {
            if (entry.getKey().startsWith("SET.props")
                && (entry.getValue() instanceof IntegerParser || entry.getValue() instanceof DoubleParser
                    || entry.getValue() instanceof ArrayParser || entry.getValue() instanceof MatrixParser)) {
                newMap.put(entry.getKey(), new RuntimePropertyParser((FieldParser) entry.getValue()));
            } else {
                newMap.put(entry.getKey(), entry.getValue());
            }
        }
        map.clear();
        map.putAll(newMap);
    }

    /**
     * Fills rules for {@link XRECallCommand}.
     *
     * @param map the map of rules
     */
    private static void putCallCommand(Map<String, ItemParser> map) {
        map.put("CALL.targetId", INTEGER_OPTIONAL);
        map.put("CALL.targetPath", STRING_OPTIONAL);
        map.put("CALL.method", STRING_REQUIRED);
        map.put("CALL.params", new ListParser(true, "method"));

        // XREView methods
        map.put("CALL.params.addChild.0", INTEGER_REQUIRED);
        map.put("CALL.params.addChild.1", INTEGER_OPTIONAL);
        map.put("CALL.params.removeChild.0", INTEGER_REQUIRED);
        map.put("CALL.params.setChildIndex.0", INTEGER_REQUIRED);
        map.put("CALL.params.setChildIndex.1", INTEGER_OPTIONAL);
        map.put("CALL.params.animate.0", INTEGER_REQUIRED);
        map.put("CALL.params.generateEvent.0", new EventParser(true));
        map.put("CALL.params.generateEvent.0.class", EMPTY_PARSER);
        map.put("CALL.params.generateEvent.0.name", STRING_REQUIRED);
        map.put("CALL.params.generateEvent.0.params", new MapParser(true, "name"));
        map.put("CALL.params.generateEvent.0.params.onReady.class", STRING_REQUIRED);
        // no params for activate and measureResource

        // XREFlash methods
        map.put("CALL.params.flashCall.0", STRING_REQUIRED);
        map.put("CALL.params.flashCall.1", new MapParser(true, null));
        map.put("CALL.params.flashCall.1.*", STRING_REQUIRED);

        // XREVideo, XRESound methods does not have parameters
        // play, pause, stop, requestStatus
        map.put("CALL.params.setVolume.0", INTEGER_REQUIRED);

        // XREApplication methods
        // generateEvent is specified already in XREView events
        // readLocalObject has no parameters
        map.put("CALL.params.writeLocalObject.0", new MapParser(true, null));
        map.put("CALL.params.writeLocalObject.0.*", STRING_REQUIRED);
        map.put("CALL.params.execExternal.0", STRING_REQUIRED);
        map.put("CALL.params.execExternal.1", STRING_REQUIRED);
        map.put("CALL.params.execExternal.2", new ListParser(true, null));
        map.put("CALL.params.execExternal.2.*", STRING_REQUIRED);
        map.put("CALL.params.execExternal.3", STRING_REQUIRED);
        map.put("CALL.params.httpRequest.0", STRING_REQUIRED);
        map.put("CALL.params.httpRequest.1", STRING_REQUIRED);
        map.put("CALL.params.httpRequest.2", new MapParser(true, null));
        map.put("CALL.params.httpRequest.2.*", STRING_REQUIRED);
        map.put("CALL.params.httpRequest.3", STRING_REQUIRED);
        map.put("CALL.params.httpRequest.4", STRING_OPTIONAL);
        // AndroidAssembly3 added support for generateAppEvent method
        map.put("CALL.params.ping.0", STRING_REQUIRED);
        map.put("CALL.params.ping.1", INTEGER_REQUIRED);
        map.put("CALL.params.generateAppEvent.0", new EventParser(true));
        map.put("CALL.params.generateAppEvent.0.class", EMPTY_PARSER);
        map.put("CALL.params.generateAppEvent.0.name", STRING_REQUIRED);
        map.put("CALL.params.generateAppEvent.0.params", new MapParser(true, "name"));
        map.put("CALL.params.generateAppEvent.0.params.onRPCCall.class", STRING_REQUIRED);
        map.put("CALL.params.generateAppEvent.0.params.onRPCCall.callGUID", STRING_REQUIRED);
        //map.put("CALL.params.generateAppEvent.0.params.onRPCCall.callParams", new ListParser(true, null));
        map.put("CALL.params.generateAppEvent.0.params.onRPCCall.callParams", new DefaultParser(true));
        //map.put("CALL.params.generateAppEvent.0.params.onRPCCall.callParams.*", new DefaultParser(true));
        map.put("CALL.params.generateAppEvent.0.params.onRPCCall.destinationSessionGUID", STRING_REQUIRED);
        map.put("CALL.params.generateAppEvent.0.params.onRPCCall.method", STRING_REQUIRED);
        map.put("CALL.params.generateAppEvent.0.params.onRPCCall.sourceSessionGUID", STRING_REQUIRED);

        map.put("CALL.params.generateAppEvent.0.params.onRPCReturn.class", STRING_REQUIRED);
        map.put("CALL.params.generateAppEvent.0.params.onRPCReturn.callGUID", STRING_REQUIRED);
        map.put("CALL.params.generateAppEvent.0.params.onRPCReturn.destinationSessionGUID", STRING_REQUIRED);
        map.put("CALL.params.generateAppEvent.0.params.onRPCReturn.method", STRING_REQUIRED);
        map.put("CALL.params.generateAppEvent.0.params.onRPCReturn.sourceSessionGUID", STRING_REQUIRED);
        map.put("CALL.params.generateAppEvent.0.params.onRPCReturn.returnVal", new DefaultParser(true));

        map.put("CALL.params.generateAppEvent.1", STRING_REQUIRED);
        
        map.put("CALL.params.log.0", STRING_REQUIRED);
    }

    /**
     * Add recourse options parser.
     *
     * @param map the map of parsers
     * @param prefix the prefix for resource options parser
     */
    private static void putResourceOptions(Map<String, ItemParser> map, String prefix) {
        prefix = prefix + ".resourceOptions";
        map.put(prefix, new ObjectParser(false, XREResourceOptions.class));
        map.put(prefix + ".horizontalAlign", new EnumParser<XREHorizontalAlignment>(XREHorizontalAlignment.class));
        map.put(prefix + ".verticalAlign", new EnumParser<XREVerticalAlignment>(XREVerticalAlignment.class));
        map.put(prefix + ".textWrap", new EnumParser<XRETextWrap>(XRETextWrap.class));
        map.put(prefix + ".textTruncStyle", new EnumParser<XRETextTruncStyle>(XRETextTruncStyle.class));
        map.put(prefix + ".stretch", new EnumParser<XREResourceStretch>(XREResourceStretch.class));
        // AndroidAssembly3 added support for text intents
        map.put(prefix + ".textStartPos", DOUBLE_ARRAY);
        map.put(prefix + ".textStopPos", DOUBLE_ARRAY);
        map.put(prefix + ".textStartChar", INTEGER_REQUIRED);
        map.put(prefix + ".textStopChar", INTEGER_REQUIRED);
    }

    /**
     * Adds common image parsers.
     *
     * @param map the map of parsers
     * @param prefix the prefix for inner parsers
     */
    private static void putImageCommons(Map<String, ItemParser> map, String prefix) {
        map.put(prefix + ".url", STRING_OPTIONAL);
        map.put(prefix + ".quality", new EnumParser<XREImageQuality>(XREImageQuality.class));
        putEventParser(map, prefix + ".onImageMetadata");
        // AndroidAssembly3 added onReady event
        putEventParser(map, prefix + ".onReady");
    }

    /**
     * Adds common animation parsers.
     *
     * @param map the map of parsers
     * @param prefix the prefix for inner parsers
     */
    private static void putAnimationCommons(Map<String, ItemParser> map, String prefix) {
        map.put(prefix + ".duration", INTEGER_REQUIRED);
        map.put(prefix + ".easing", new EnumParser<XREAnimationEasingFunction>(XREAnimationEasingFunction.class));
        putEventParser(map, prefix + ".onComplete");
    }

    /**
     * Adds event handler parsers.
     *
     * @param map the map of parsers
     * @param prefix the prefix for inner parsers
     */
    private static void putEventParser(Map<String, ItemParser> map, String prefix) {
        map.put(prefix, new MapParser(false, null));
        map.put(prefix + ".commandSequence", INTEGER_OPTIONAL);
        map.put(prefix + ".filter", new ListParser(false, null));
        map.put(prefix + ".filter.*", new MapParser(false, null));
        map.put(prefix + ".filter.*.*", STRING_OPTIONAL);
    }

    /**
     * Adds {@link XREView} event parsers.
     *
     * @param map the map of parsers
     * @param prefix the prefix for inner parsers
     */
    private static void putXREViewEvents(Map<String, ItemParser> map, String prefix) {
        // XREView standard events
        putEventParser(map, prefix + ".onMouseIn");
        putEventParser(map, prefix + ".onMouseOut");
        putEventParser(map, prefix + ".onMouseUp");
        putEventParser(map, prefix + ".onMouseDown");
        putEventParser(map, prefix + ".onMouseWheel");
        putEventParser(map, prefix + ".onKeyUp");
        putEventParser(map, prefix + ".onKeyDown");
        putEventParser(map, prefix + ".onKeyStartRepeating");
        putEventParser(map, prefix + ".onActivate");
        putEventParser(map, prefix + ".onDeactivate");
        putEventParser(map, prefix + ".onResourceMeasured");

        // XREView preview events
        putEventParser(map, prefix + ".onPreviewMouseIn");
        putEventParser(map, prefix + ".onPreviewMouseOut");
        putEventParser(map, prefix + ".onPreviewMouseUp");
        putEventParser(map, prefix + ".onPreviewMouseDown");
        putEventParser(map, prefix + ".onPreviewMouseWheel");
        putEventParser(map, prefix + ".onPreviewKeyUp");
        putEventParser(map, prefix + ".onPreviewKeyDown");
        putEventParser(map, prefix + ".onPreviewKeyStartRepeating");
    }

    /**
     * Add {@link XREVideo} event parsers.
     *
     * @param map the map of parsers
     * @param prefix the prefix for inner parsers
     */
    private static void putVideoEvents(Map<String, ItemParser> map, String prefix) {
        putEventParser(map, prefix + ".onVideoMetadata");
        putEventParser(map, prefix + ".onVideoStatus");
        putEventParser(map, prefix + ".onDynamicTransitionComplete");
        putEventParser(map, prefix + ".onStreamProgress");
        putEventParser(map, prefix + ".onStreamPlaying");
        putEventParser(map, prefix + ".onStreamPaused");
        putEventParser(map, prefix + ".onStreamBuffering");
        putEventParser(map, prefix + ".onStreamClose");
        putEventParser(map, prefix + ".onStreamComplete");
    }

    /**
     * Add {@link XREApplication} event parsers.
     *
     * @param map the map of parsers
     * @param prefix the prefix for inner parsers
     */
    private static void putApplicationEvents(Map<String, ItemParser> map, String prefix) {
        putEventParser(map, prefix + ".onReadLocalObject");
        putEventParser(map, prefix + ".onExecExternalResult");
        putEventParser(map, prefix + ".onHTTPResponse");
        putEventParser(map, prefix + ".onHTTPError");
        putEventParser(map, prefix + ".onRPCCall");
        putEventParser(map, prefix + ".onRPCReturn");
        // AndroidAssembly3 added more event handlers
        putEventParser(map, prefix + ".onError");
        putEventParser(map, prefix + ".onPingComplete");
        putEventParser(map, prefix + ".onGetComplete");

    }

    /**
     * Enum that indicates how parsed value will be set to target instance.
     *
     * @author 7realm
     * @version 1.0
     */
    public enum SetterType {
        /** Indicates that value must be set as field. */
        FIELD,
        /** Indicates that value must be set as list item. */
        LIST,
        /** Indicates that value must be set as map entry. */
        MAP;
    }
}
